package com.excel2objects.common.excel.engine;

import com.excel2objects.common.excel.annotations.ExcelColumn;
import com.excel2objects.common.excel.convertors.Converter;
import com.excel2objects.common.excel.exceptions.UnparsbleException;
import com.excel2objects.common.excel.util.Configuration;
import java.beans.PropertyDescriptor;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class ExcelParser<T> implements Serializable {

    private static Logger logger = Logger.getLogger(ExcelParser.class);
    private int skipNoOfRows = 0;
    private Map<String, Converter> converterMap = new HashMap<String, Converter>();

    public ExcelParser(int skipNoOfRows) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        this.skipNoOfRows = skipNoOfRows;
        this.init();

    }

    public void registerConverter(String type, Converter objInstance) throws InstantiationException, IllegalAccessException {
        this.converterMap.put(type, objInstance);

    }

    @SuppressWarnings("unchecked")
    private void init() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Class<Converter> obj = (Class<Converter>) Class.forName("com.excel2objects.common.excel.convertors.IntegerConverter");
        Converter objInstance = obj.newInstance();
        this.converterMap.put("java.lang.Integer", objInstance);

        obj = (Class<Converter>) Class.forName("com.excel2objects.common.excel.convertors.FloatConverter");
        objInstance = obj.newInstance();
        this.converterMap.put("java.lang.Float", objInstance);

        obj = (Class<Converter>) Class.forName("com.excel2objects.common.excel.convertors.BooleanConverter");
        objInstance = obj.newInstance();
        this.converterMap.put("java.lang.Boolean", objInstance);

        obj = (Class<Converter>) Class.forName("com.excel2objects.common.excel.convertors.DateConverter");
        objInstance = obj.newInstance();
        this.converterMap.put("java.util.Date", objInstance);

        obj = (Class<Converter>) Class.forName("com.excel2objects.common.excel.convertors.StringConverter");
        objInstance = obj.newInstance();
        this.converterMap.put("java.lang.String", objInstance);

        obj = (Class<Converter>) Class.forName("com.excel2objects.common.excel.convertors.DoubleConverter");
        objInstance = obj.newInstance();
        this.converterMap.put("java.lang.Double", objInstance);
    }

    public List<T> getObjects(InputStream is, Class<T> classType) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException, UnparsbleException {

        try {
            Map fields = new LinkedHashMap();
            for (Field f : classType.getDeclaredFields()) {
                ExcelColumn annotation = f.getAnnotation(ExcelColumn.class);
                if (annotation != null) {
                    fields.put(Integer.valueOf(annotation.order()),
                            f.getName());
                }
            }
            List<T> data;
            ExcelParser<T> parser = new ExcelParser<T>(1);
            if (Configuration.FILE_TYPE == 1) {
                Workbook wb = new HSSFWorkbook(is);
                data = parser.getObjects(wb.getSheetAt(Configuration.DEFAULT_SHEET), classType, fields);
            } else {
               XSSFWorkbook wb = new XSSFWorkbook(is);
                //SXSSFWorkbook wb = new SXSSFWorkbook(new XSSFWorkbook(is), 100);
                //SXSSFWorkbook wb = new SXSSFWorkbook(new XSSFWorkbook(is));
                data = parser.getObjects(wb.getSheetAt(Configuration.DEFAULT_SHEET), classType, fields);
            }


            return data;
        } catch (Exception ex) {
            logger.error("error", ex);
            throw new UnparsbleException("Error al leer los datos por reflexion", ex);
        }
    }

    public List<T> getObjects(Sheet sheet, Class<T> className, Map<Integer, String> map) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException, UnparsbleException {
        List<T> listObjects = new ArrayList<T>();
        for (int i = 0; i < sheet.getPhysicalNumberOfRows(); i++) {
            logger.debug("Processing row" + i);
            if (i < this.skipNoOfRows) {
                continue;
            }
            T obj = this.createObject(className, sheet.getRow(i), map);
            listObjects.add(obj);
        }
        return listObjects;
    }

    @SuppressWarnings("unchecked")
    private T createObject(Class<T> className, Row row, Map<Integer, String> map) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException, UnparsbleException {

        Class<T> obj = (Class<T>) Class.forName(className.getName());
        T objInstance = obj.newInstance();
        logger.debug("Setting properties for class" + className.getName());
        if (row == null) {
            return null;
        }
        for (int i = 0; i < row.getPhysicalNumberOfCells(); i++) {
            logger.debug("Processing cell" + i);

            String propertyName = map.get(new Integer(i + 1));
            logger.debug("Processing cell propertyName" + propertyName);
            Cell cell = row.getCell(i);
            if (cell == null) {
                continue;
            }
            if (propertyName == null || propertyName.trim().equalsIgnoreCase("")) {
                continue;
            }
            Object value;
            Object cellValue = this.getCellValue(cell);
            if (cellValue == null) {
                continue;
            }
            logger.debug("Getting descriptor for property propertyName" + propertyName);
            PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(objInstance, propertyName);
            if (descriptor == null) {
                throw new UnparsbleException("Check if property" + propertyName + " exists");
            }

            Class<?> type = descriptor.getPropertyType();
            Converter<?> converter = this.converterMap.get(type.getName());
            if (converter == null) {
                throw new UnparsbleException("No Converters for" + type.getName() + " exists");
            }

            try {
                value = converter.from(cellValue);
            } catch (Exception e) {
                logger.error(e);
                value = null;
            }
            PropertyUtils.setSimpleProperty(objInstance, propertyName, value);
        }
        return objInstance;
    }

    private Object getCellValue(Cell cell) {
        if (cell == null) {
            return null;
        }
        if (cell.getCellType() == Cell.CELL_TYPE_BLANK) {
            return "";
        }
        if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
            return cell.getNumericCellValue();
        }
        if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
            return cell.getStringCellValue().replaceAll("'", "");
        }
        return null;
    }
}
